/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ru.adonixis.animatedmenuitems;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.List;

/**
 * animator base component
 */
public abstract class AnimatorComponent extends ComponentContainer implements Component.LayoutRefreshedListener, AnimatorValue.ValueUpdateListener,
        AnimatorValue.StateChangedListener, Component.DrawTask, Component.TouchEventListener {
    protected AnimatorValue clickAnimator;
    protected boolean isAnimatorRunning = false;
    protected long animatorDurationTime;
    protected long animatorStartTime;
    private List<TouchHolder> touchHolders = new ArrayList<>();
    private Paint paint = new Paint();
    private float rippleAlpha = 0.2f;
    private boolean rippleEnabled = true;
    private int rippleColor = Color.WHITE.getValue();

    public AnimatorComponent(Context context) {
        super(context);
    }

    public AnimatorComponent(Context context, AttrSet attrSet) {
        super(context, attrSet);
    }

    public AnimatorComponent(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    private void init(AttrSet attrSet) {
        setLayoutRefreshedListener(this);
        setTouchEventListener(this);
        addDrawTask(this, BETWEEN_BACKGROUND_AND_CONTENT);
        clickAnimator = new AnimatorValue();
        clickAnimator.setValueUpdateListener(this);
        clickAnimator.setStateChangedListener(this);
        rippleColor = AttrUtils.getColorFromAttr(attrSet, "rippleColor", rippleColor);
        rippleAlpha = AttrUtils.getFloatFromAttr(attrSet, "rippleAlpha", rippleAlpha);
        rippleEnabled = AttrUtils.getBooleanFromAttr(attrSet, "rippleEnabled", rippleEnabled);
        paint.setAntiAlias(true);
        paint.setColor(Color.WHITE);
    }

    /**
     * when current time after duration time, return false
     *
     * @return is animator running
     */
    public boolean isAnimatorRunning() {
        if (System.currentTimeMillis() - animatorStartTime > animatorDurationTime) {
            isAnimatorRunning = false;
        }
        return isAnimatorRunning;
    }

    /**
     * set touch effect with ripple
     *
     * @param rippleEnabled ripple enabled
     */
    public void setRippleEnabled(boolean rippleEnabled) {
        this.rippleEnabled = rippleEnabled;
    }

    /**
     * set ripple alpha
     *
     * @param rippleAlpha ripple alpha 0~1
     */
    public void setRippleAlpha(float rippleAlpha) {
        this.rippleAlpha = rippleAlpha;
    }

    /**
     * set ripple color
     *
     * @param rippleColor ripple color
     */
    public void setRippleColor(int rippleColor) {
        this.rippleColor = rippleColor;
        paint.setColor(new Color(rippleColor));
    }

    /**
     * start animator
     */
    protected void startAnimator() {
        if (isAnimatorRunning()) {
            return;
        }
        clickAnimator.start();
    }

    @Override
    public void onStart(Animator animator) {
        animatorStartTime = System.currentTimeMillis();
        isAnimatorRunning = true;
    }

    @Override
    public void onStop(Animator animator) {
    }

    @Override
    public void onCancel(Animator animator) {
    }

    @Override
    public void onEnd(Animator animator) {
        isAnimatorRunning = false;
    }

    @Override
    public void onPause(Animator animator) {
    }

    @Override
    public void onResume(Animator animator) {
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        long currentTime = System.currentTimeMillis();
        calculateTouchHolders(currentTime);
        if (touchHolders != null && touchHolders.size() > 0) {
            for (TouchHolder touchHolder : touchHolders) {
                float radius = (getWidth() > getHeight() ? getHeight() : getWidth()) / 2f;
                int centerX = getWidth() / 2;
                int centerY = getHeight() / 2;
                if (!touchHolder.isUp) {
                    if (currentTime > touchHolder.downTime + 300) {
                        paint.setAlpha(rippleAlpha);
                        canvas.drawCircle(centerX, centerY, radius, paint);
                    } else {
                        float fraction = 1f * (currentTime - touchHolder.downTime) / 300;
                        paint.setAlpha(rippleAlpha * fraction);
                        float x = touchHolder.touchX + (centerX - touchHolder.touchX) * fraction;
                        float y = touchHolder.touchY + (centerY - touchHolder.touchY) * fraction;
                        canvas.drawCircle(x, y, radius * fraction, paint);
                    }
                } else {
                    if (touchHolder.downTime + 300 > touchHolder.upTime) {
                        if (currentTime > touchHolder.downTime + 300) {
                            float fraction = 1 - 1f * (currentTime - touchHolder.downTime - 300) / 300;
                            paint.setAlpha(rippleAlpha * fraction);
                            canvas.drawCircle(centerX, centerY, radius, paint);
                        } else {
                            float fraction = 1f * (currentTime - touchHolder.downTime) / 300;
                            paint.setAlpha(rippleAlpha * fraction);
                            float x = touchHolder.touchX + (centerX - touchHolder.touchX) * fraction;
                            float y = touchHolder.touchY + (centerY - touchHolder.touchY) * fraction;
                            canvas.drawCircle(x, y, radius * fraction, paint);
                        }
                    } else {
                        float fraction = 1 - 1f * (currentTime - touchHolder.upTime) / 300;
                        paint.setAlpha(rippleAlpha * fraction);
                        canvas.drawCircle(centerX, centerY, radius, paint);
                    }
                }
            }
            getContext().getUITaskDispatcher().asyncDispatch(() -> invalidate());
        }
    }

    private class TouchHolder {
        private float touchX;
        private float touchY;
        private boolean isUp;
        private long downTime;
        private long upTime;
    }

    private void calculateTouchHolders(long time) {
        List<TouchHolder> removeTouchHolders = new ArrayList<>();
        if (touchHolders != null && touchHolders.size() > 0) {
            for (TouchHolder touchHolder : touchHolders) {
                if (touchHolder.isUp && time > touchHolder.downTime + 600 && time > touchHolder.upTime + 300) {
                    removeTouchHolders.add(touchHolder);
                }
            }
        }
        if (removeTouchHolders != null && removeTouchHolders.size() > 0) {
            if (removeTouchHolders != null && removeTouchHolders.size() > 0) {
                for (TouchHolder touchHolder : removeTouchHolders) {
                    touchHolders.remove(touchHolder);
                }
            }
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (touchEvent.getPointerCount() == 1) {
            if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN && rippleEnabled) {
                TouchHolder touchHolder = new TouchHolder();
                touchHolder.downTime = System.currentTimeMillis();
                touchHolder.touchX = getTouchX(touchEvent, 0);
                touchHolder.touchY = getTouchY(touchEvent, 0);
                touchHolders.add(touchHolder);
                invalidate();
            } else if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP || touchEvent.getAction() == TouchEvent.CANCEL) {
                if (touchHolders != null && touchHolders.size() > 0) {
                    TouchHolder touchHolder = touchHolders.get(touchHolders.size() - 1);
                    if (!touchHolder.isUp) {
                        touchHolder.upTime = System.currentTimeMillis();
                        touchHolder.isUp = true;
                    }
                }
            } else if (touchEvent.getAction() == TouchEvent.POINT_MOVE && moveOutOfBounds(getTouchX(touchEvent, 0), getTouchY(touchEvent, 0))) {
                if (touchHolders != null && touchHolders.size() > 0) {
                    TouchHolder touchHolder = touchHolders.get(touchHolders.size() - 1);
                    if (!touchHolder.isUp) {
                        touchHolder.upTime = System.currentTimeMillis();
                        touchHolder.isUp = true;
                    }
                }
            }
        }
        return true;
    }

    private boolean moveOutOfBounds(float touchX, float touchY) {
        if (touchX < 0 || touchX > getWidth() || touchY < 0 || touchY > getHeight()) {
            return true;
        } else {
            return false;
        }
    }

    private float getTouchX(TouchEvent touchEvent, int index) {
        float touchX = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                touchX = touchEvent.getPointerScreenPosition(index).getX() - xy[0];
            } else {
                touchX = touchEvent.getPointerPosition(index).getX();
            }
        }
        return touchX;
    }

    private float getTouchY(TouchEvent touchEvent, int index) {
        float touchY = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                touchY = touchEvent.getPointerScreenPosition(index).getY() - xy[1];
            } else {
                touchY = touchEvent.getPointerPosition(index).getY();
            }
        }
        return touchY;
    }
}
